package com.gitee.booting.dspi;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@EnableWebMvc
public class Endpoint {
    private final ApplicationContext context;

    private static final Map<String, Object> PROVIDERS = new ConcurrentHashMap<>();
    private static final Map<String, Map<String, Map<String, Method>>> METHODS = new ConcurrentHashMap<>();

    @Autowired
    public Endpoint(ApplicationContext context) {
        // We are in a child context, but we need to find necessary beans in default container.
        this.context = context.getParent();
    }

    @RequestMapping(path = "/{i}/{impl}/{method}/{types}")
    public Object home(@PathVariable String i, @PathVariable String impl, @PathVariable String method,
                       @PathVariable String types,
                       @RequestBody(required = false) Object[] args) throws ClassNotFoundException,
        NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        METHODS.computeIfAbsent(impl, x -> new ConcurrentHashMap<>());
        METHODS.get(impl).computeIfAbsent(method, x -> new ConcurrentHashMap<>());
        if (!METHODS.get(impl).get(method).containsKey(types)) {
            ClassLoader cl = context.getClass().getClassLoader();
            Class<?> type = cl.loadClass(i);
            Class<?> provider = cl.loadClass(impl);
            List<Class<?>> parameterTypes = new ArrayList<>();
            for (String parameterType : types.split(Extensions.COMMA)) {
                if (!Extensions.MINUS.equals(parameterType)) {
                    parameterTypes.add(cl.loadClass(parameterType));
                }
            }
            Method target;
            for (Object instance : context.getBeansOfType(type).values()) {
                if (provider.isAssignableFrom(instance.getClass())) {
                    PROVIDERS.putIfAbsent(impl, instance);
                    target = instance.getClass().getMethod(method, parameterTypes.toArray(new Class<?>[0]));
                    METHODS.get(impl).get(method).putIfAbsent(types, target);
                    break;
                }
            }
        }
        return METHODS.get(impl).get(method).get(types).invoke(PROVIDERS.get(impl), args);
    }
}
